
import { fetchAvatarUrl } from './utils';

/**
 * 弹幕抽奖机 751714 / 7734200
 * 
 * 恒定大小: 1280 x 720
 * 
 * 流程:
 * 1. monitor 需要添加告诉屏幕, 房间号
 * 2. 屏幕这里来创建资源、开启资源、绑定资源
 */

const PHASE_NO_READY = 0;
const PHASE_SEND_DANMU = 1;
const PHASE_SEND_CLOSE = 2;
const PHASE_BEGIN_LOTTERY = 10;
const PHASE_FINISH_LOTTERY = 20;

const STRATEGY_SCORE = "score";
const STRATEGY_CUSTOMER = "customer";
const STRATEGY_DEFAULT = "default";

class Arcade {
  /** UP 主的 uid, 用来看是不是本房间的牌子 */
  upUId = 0;
  words = '666'; // 弹幕抽奖的语句

  /**
   * 阶段:
   * 0 - 未开始
   * 1 - 前面计时发弹幕阶段, [0] 点开始或者 [2] 点继续发弹幕可以进入,
   *     可选择计时或者不计时, 也可以中途计时. 发送指定口令可以将用户放入抽奖池.
   * 2 - 计时结束阶段. [1] 计时结束或者按停止发送弹幕进入.
   * 10 - 开始抽奖
   * 20 - 抽奖结束
   */
  phase = PHASE_NO_READY;

  /**
   * 倒计时结束时的时间戳, timestamp 到多少是结束
   */
  countdown = 0;
  countdownMax = 0;

  /** @type {Panel} */
  panel = null;
  /**
   * uId - Customer
   */
  customers = {};
  /**
   * uId - 礼物的总价值
   */
  gifts = {};
  /**
   * @type {Customer[]} 抽奖池
   */
  participants = [];
  /**
   * @type {Customer[]} 开始抽奖时才生成的数据
   */
  candidates = [];
  /**
   * @type {Customer[]} 胜利者
   */
  winners = [];
  /**
   * @type {Rule[]} 规则
   */
  rules;
  /**
   * 抽奖策略, 是每个分获胜等概率, 还是每个人扣 HP 概率相等
   */
  strategy = STRATEGY_DEFAULT;

  constructor(param) {
    this.panel = param.panel;

    this.rules = [
      new Rule('start', null, null, 10, '='),
      new Rule('start', '牌', 1, 1, '+'),
      new Rule('start', '牌', 11, 1, '+'),
      new Rule('start', '牌', 21, 1, '+'),
      new Rule('start', '舰', null, 1, '+'),
      new Rule('start', '礼', 1, 1, '+*'),

      new Rule('stage', 'fail', null, 2, '+'),
      new Rule('stage', '舰', null, 1, '+'),
      new Rule('stage', 'success', null, 3, '/'),
      new Rule('stage', '礼', 1, 1, '+*'),
    ];
  }

  setWords(words) {
    this.words = words;
    this.panel.setWords(words);
  }

  reset() {
    if (!this.panel.isReady()) { return; }

    this.phase = PHASE_NO_READY;
    for (let uId in this.customers) {
      this.customers[uId].score = -1;
      this.customers[uId].lastScore = -1;
    }
    this.participants = [];
    this.candidates = [];
    this.winners = [];
    // 通知 panel, 重置
    this.panel.reset();
  }

  begin(data) {
    if (!this.panel.isReady()) { return; }

    // 开始发弹幕
    if (this.phase == PHASE_BEGIN_LOTTERY || this.phase == PHASE_FINISH_LOTTERY) {
      // 上次抽奖的收尾
      for (let uId in this.customers) {
        /** @type {Customer} */
        let customer = this.customers[uId];
        if (this.participants.find(c => c == customer)) {
          // 上次参加了
          customer.lastScore = customer.score;
          customer.success = this.winners.find(c => c == customer) != null;
        } else {
          customer.success = null;
        }
      }
      this.participants = [];
    }
    this.phase = PHASE_SEND_DANMU;
    this.candidates = [];
    
    // 要不要启动倒计时, 看 data.countdown 是不是 number | null
    if (data.countdown > 0) {
      this.countdownMax = data.countdown * 1000;
      this.countdown = new Date().getTime() + this.countdownMax;
    }

    // 通知 panel
    this.panel.begin();
  }
  
  ready() {
    if (this.phase != PHASE_SEND_DANMU || !this.panel.isReady()) {
      return;
    }

    // 弹幕结束
    this.phase = PHASE_SEND_CLOSE;
    this.participants.forEach((c) => this.calcScore(c));
    this.candidates = this.participants.slice(); // copy
    // 通知 panel
    this.panel.ready();
  }

  /**
   * @param {number} remain 需求是减到几个人
   * @param {string} strategy 扣分策略
   */
  startLottery(remain, strategy = null) {
    if (this.participants.length <= 1) {
      return;
    }
    if (strategy) {
      this.strategy = (strategy === STRATEGY_SCORE) ? STRATEGY_SCORE : (strategy === STRATEGY_CUSTOMER) ? STRATEGY_CUSTOMER : STRATEGY_DEFAULT;
    }

    var count = (this.winners.length) ? this.winners.length : this.participants.length;
    if (remain != null && count <= remain) {
      return;
    }

    this.candidates = (this.winners.length) ? this.winners.slice() : this.participants.slice();
    this.winners = [];
    // 通知 panel 记录开始状态
    this.panel.beforeLottery();
  }

  doLottery(remain) {
    if (this.strategy === STRATEGY_SCORE) {
      this.doLotteryByScoreStrategy(remain);
    } else if (this.strategy === STRATEGY_CUSTOMER) {
      this.doLotteryByCustomerStrategy(remain);
    } else {
      // 默认: 12 人及以上用 customer 策略, 以下用 score 策略
      if (remain >= 12) {
        this.doLotteryByCustomerStrategy(remain);
      } else if (this.candidates.length >= 12) {
        this.doLotteryByCustomerStrategy(12);
        this.doLotteryByScoreStrategy(remain);
      } else {
        this.doLotteryByScoreStrategy(remain);
      }
    }
    this.winners = this.candidates.slice();
    this.panel.afterLottery();
  }

  doLotteryByScoreStrategy(remain) {
    /** @type {Customer[]} */
    const table = [];
    this.candidates.forEach((c) => {
      var hp = c.hp;
      for (var i = 0; i < hp; i++) {
        table.push(c);
      }
    });
    while (this.candidates.length > remain) {
      var delIdx = parseInt(Math.random() * table.length);
      var customer = table[delIdx];
      table.splice(delIdx, 1);
      customer.hp--;
      if (customer.hp == 0) {
        for (var i = 0; i < this.candidates.length; i++) {
          if (this.candidates[i] == customer) {
            this.candidates.splice(i, 1);
            break;
          }
        }
      }
    }
  }

  doLotteryByCustomerStrategy(remain) {
    const table = this.candidates.slice();
    while (this.candidates.length > remain) {
      var delIdx = parseInt(Math.random() * table.length);
      var customer = table[delIdx];
      customer.hp--;
      if (customer.hp == 0) {
        for (var i = 0; i < this.candidates.length; i++) {
          if (this.candidates[i] == customer) {
            this.candidates.splice(i, 1);
            break;
          }
        }
        table.splice(delIdx, 1);
      }
    }
  }

  /**
   * 检查弹幕文本、判断时间是否在抽奖时间内. 如果符合, 交给 panel.
   * @param {any} danmu 弹幕数据
   */
  onDanmuReceived(danmu) {
    // 登记
    const uId = danmu.uId;
    /** @type {Customer | null} */
    let customer = this.customers[uId];
    if (customer) {
      customer.update(danmu, this.upUId);
    } else {
      this.customers[uId] = customer = new Customer(danmu, this.upUId);
      customer.onInfoUpdated = (c) => this.onCustomerUpdated(c);
    }

    // 弹幕抽奖部分校验
    if (this.phase != PHASE_SEND_DANMU) {
      return;
    }
    let text = danmu.text.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
    if (text != this.words && this.words != '') { // TODO
      return;
    }

    // 加入抽奖池
    if (!this.participants.find(c => c == customer)) {
      this.participants.push(customer);
      // 通知 panel
      this.panel.addParticipant(customer);
    }
  }

  onGiftReceived(gift) {
    // console.log(`${gift.uName}(${gift.uId}) 送了 ${gift.gCount} 个 ${gift.gift}, 价值 ${gift.price}`);
    if (!gift.price) {
      return;
    }
    if (this.gifts[gift.uId] != null) {
      this.gifts[gift.uId] += gift.price * gift.gCount;
    } else {
      this.gifts[gift.uId] = gift.price * gift.gCount;
    }
  }

  /**
   * @param {Customer} customer 
   */
  onCustomerUpdated(customer) {
    if (this.phase != PHASE_SEND_DANMU) {
      return;
    }
    // 如果这个人发弹幕抽奖了, 它对应的 chunk 信息要改
    if (this.participants.find(c => c == customer)) {
      this.panel.updateParticipant(customer);
    }
  }

  /**
   * 计算礼物和分数等
   * @param {Customer} participant 
   */
  calcScore(participant) {
    participant.gifts = 0;
    if (this.gifts[participant.uId] && this.gifts[participant.uId] > 0) {
      let s = parseInt(this.gifts[participant.uId] / 1000);
      participant.gifts += s;
      this.gifts[participant.uId] -= s * 1000;
    }

    if (participant.lastScore == -1) {
      participant.score = 0;
      this.rules.forEach((rule) => {
        if (rule.type != 'start') return;
        let param = {};
        if (rule.matchFunc(participant, param)) {
          participant.score = rule.scoreFunc(participant.score, param);
        }
      });
    } else {
      participant.score = participant.lastScore;
      this.rules.forEach((rule) => {
        if (rule.type != 'stage') return;
        let param = {};
        if (rule.matchFunc(participant, param)) {
          participant.score = rule.scoreFunc(participant.score, param);
        }
      });
    }
    participant.hp = participant.score;
  }

  tick() {
    if (this.countdown > 0 && new Date().getTime() > this.countdown) {
      this.countdown = 0;
      this.ready();
    }
  }
}

/**
 * 规则 初始篇
 * 
 * [刚开始每人基础分 10 分]   type:'start', match:null, matchParam:null, score:10, scoreCalc:'='
 * [粉丝牌 1 级及以上 +1 分]   type:'start', match:'牌', matchParam:1, score:1, scoreCalc:'+'
 * [粉丝牌 11 级及以上 +1 分]   type:'start', match:'牌', matchParam:11, score:1, scoreCalc:'+'
 * [粉丝牌 21 级及以上 +1 分]   type:'start', match:'牌', matchParam:21, score:1, scoreCalc:'+'
 * [舰长 +1 分]   type:'start', match:'舰', matchParam:null, score:1, scoreCalc:'+'
 * [统计礼物, 每 1 元礼物 +1 分] type:'start', match:'礼', matchParam:1, score:1, scoreCalc:'+*'
 * 
 * 普通用户开始时  10 分
 * 普通用户牌子 11 级时  12 分
 * 舰长用户牌子 21 级时  14 分
 * 
 * ===
 * 规则 每轮过后
 * 
 * [上次没获奖的用户, 每人加 2 分]   type:'stage', match:'fail', matchParam:null, score:2, scoreCalc:'+'
 * [舰长 +1 分]   type:'stage', match:'舰', matchParam:null, score:1, scoreCalc:'+'
 * [上次获奖的用户, 分数除以 4]   type:'stage', match:'success', matchParam:null, score:4, scoreCalc:'/'
 * [统计礼物, 每 1 元礼物 +1 分] type:'stage', match:'礼', matchParam:1, score:1, scoreCalc:'+*'
 * 
 * ===
 * 黑幕 给特定用户增加、减少、设置成指定分数. 下个版本再做
 */
class Rule {
  // 类型
  type = '';
  /** @type {string} */
  match = null;
  /** @type {number | null} */
  matchParam = null;
  score = 0;
  scoreCalc = '=';

  /** @type {function(number, object) : number} */
  scoreFunc;
  /** @type {function(Customer, object) : boolean} */
  matchFunc;

  constructor(type, match, matchParam, score, scoreCalc) {
    this.type = type;
    this.match = match;
    this.matchParam = matchParam;
    this.score = score;
    this.scoreCalc = scoreCalc;

    switch (match) {
      case '牌': {
        this.matchFunc = (customer) => customer.medal >= this.matchParam;
      } break;
      case '舰': {
        this.matchFunc = (customer) => customer.type == 3;
      } break;
      case '管': {
        this.matchFunc = (customer) => customer.admin;
      } break;
      case '礼': {
        this.matchFunc = (customer, param) => {
          let times = parseInt(customer.gifts / this.matchParam);
          param.times = times;
          return times > 0;
        };
      } break;
      case 'fail': {
        this.matchFunc = (customer) => customer.success == false;
      } break;
      case 'success': {
        this.matchFunc = (customer) => customer.success == true;
      } break;
      default: {
        this.matchFunc = () => true;
      } break;
    }

    switch (scoreCalc) {
      case '+': this.scoreFunc = (oldScore) => oldScore + this.score; break;
      case '+*': this.scoreFunc = (oldScore, object) => oldScore + this.score * object.times; break;
      case '/': this.scoreFunc = (oldScore) => {
        let s = parseInt(oldScore / this.score);
        return (s < 1) ? 1 : s;
      }; break;
      case '=': default: this.scoreFunc = (oldScore) => this.score; break;
    }
  }

  collectInfo() {
    const { type, match, matchParam, score, scoreCalc } = this;
    return { type, match, matchParam, score, scoreCalc };
  }

  toString() {
    if (this.match == null) {
      return `初始 ${this.score} 分`;
    }

    let text1 = '', text2 = '';
    switch (this.match) {
      case '牌': { text1 = `粉丝牌等级大于等于 ${this.matchParam} 级的`; } break;
      case '舰': { text1 = '舰长'; } break;
      case '管': { text1 = '房管'; } break;
      case '礼': { text1 = `送礼物的每 ${this.matchParam * 1000} 金瓜子`; } break;
      case 'fail': { text1 = '上一轮没中奖的'; } break;
      case 'success': { text1 = '上一轮中奖的'; } break;
      default: { text1 = `满足 '${this.match}' 规则 '${this.matchParam}' 条件的`;} break;
    }

    switch (this.scoreCalc) {
      case '+': text2 = `加 ${this.score} 分`; break;
      case '+*': text2 = `加 ${this.score} 分`; break;
      case '/': text2 = `分数除以 ${this.score}`; break;
      case '=': text2 = `重置为 ${this.score} 分`; break;
    }

    return text1 + text2;
  }
}

class Customer {
  // 用户信息
  /** @type {number} */
  uId = null;
  /** @type {string} */
  uName = null;
  /** 是不是房管 */
  admin = false;
  /** 普通用户 - 0, 舰长 - 3 */
  type = 0;
  /** 牌子等级 */
  medal = 0;
  /** 当前轮计算的礼物价值数, 这个值会变 */
  gifts = 0;
  /** @type {function (Customer) : any} */
  onInfoUpdated = (c) => null

  // 抽奖信息
  lastScore = -1;
  score = -1;
  /** @type {boolean | null} 上次赢了没, null: 上次没参加, 有可能上上次参加了 */
  success = null;
  /** 正式抽奖时, hp 开始值 = score */
  hp = 0;

  constructor(danmu, upUId) {
    this.uId = danmu.uId;
    this.uName = danmu.uName;
    this.admin = danmu.admin;
    this.type = danmu.type;
    if (danmu.medal == upUId) {
      this.medal = danmu.mLv;
    }
  }
  update(danmu, upUId) {
    let updated = false;

    if (this.admin != danmu.admin) {
      this.admin = danmu.admin;
      updated = true;
    }
    if (this.type != danmu.type) {
      this.type = danmu.type;
      updated = true;
    }
    if (danmu.medal == upUId && this.medal != danmu.mLv) {
      this.medal = danmu.mLv;
      updated = true;
    }

    if (updated) {
      this.onInfoUpdated(this);
    }
  }
  collectInfo() {
    const { uId, uName, score, hp, gifts } = this;
    return { uId, uName, score, hp, gifts };
  }
}


/**
 * 绘画
 */

const layout = {
  // 场景 1 的摆放位置
  chunks1 : {
    startX : 420,
    startY : 180,
    offX : -30,
    offY : 60,
    colCount : 8,
    colOffX : -200,
    colOffY : 0
  },
  title1 : {
    x : 60,
    y : 50,
    fontSize : 60,
  },
  subTitle1 : {
    x : 60,
    y : 120,
    fontSize : 30,
    paddingX : 10,
    wordsFontSize : 36,
  },
  topText1 : {
    x : 1240,
    y : 104,
    fontSize : 20,
    numX : 1240,
    numY : 35,
    numFontSize : 80,
  },
  numText1 : {
    x : 641,
    y : 770,
    fontSize : 320,
  },
  addyImg1 : {
    x : 785,
    y : 97
  },
  // 场景 2 的摆放位置
  chunks2 : {
    startX : 340,
    startY : 180,
    offX : -30,
    offY : 60,
    colCount : 8,
    colOffX : 200,
    colOffY : 0
  },
  title2 : {
    x : 605,
    y : 70,
    fontSize : 60
  },
  colors : {
    stage1Bg : '#2DB4C5', // 蓝
    stage1BgD1 : '#2A9FAB', // 蓝 暗1
    stage2Bg : '#F24440', // 红
    stage2BgD1 : '#D23634', // 红 暗1
  }
};

/**
 * 界面部分有两类:
 * 1. 发弹幕界面  2. 抽奖界面.
 * 在界面进行转换的时候有转场动画, 转场途中不能再进行别的改场景的操作.
 */
class Panel {
  /** @type {HTMLCanvasElement} */
  top = null;
  /** @type {Arcade} */
  arcade = null;
  /** @type {Chunk[]} */
  chunks = [];
  participantPermit = false;
  /** @type {string} */
  word = null;
  /** @type {string} 最下面那横栏说明规则的文字 */
  ruleText = '';
  /** 最底下那个文本移动时文字上次移动的时间 */
  bottomUpdateTime = 0;

  spRag;
  // 所有场景公用的
  spBgw; // spBgw 白色底色
  spTopText; spTopNum; // 场景一是说剩余时间, 场景二是说剩余幸存者个数
  spTopBar; spTopBarR; // 上面的条 (背景), 和条剩下的
  spBottomBg; spBottomText; // 下面的条和文字
  // 发弹幕界面 背景的东西
  // 场景一
  spBg1; spBg1d; // spBg1 蓝背景, spBg1d 蓝黑色部分
  spTitle1; spSubTitle1a; spSubTitle1b; spWords; // 标题, 副标题
  spNum1; spImg1; // 右边的, 一个显示参与人数, 一个就是张图
  // 场景二
  // 抽奖界面 背景的东西
  spBg2; spBg2d; // spBg2 红背景, spBg2d 暗红色部分
  spTitle2;

  currentStage = 1; // 1. 发弹幕界面  2. 抽奖界面
  nextStage = 0; // 转场时用到的, 从哪个界面到哪个界面, 0 表示当前没在转场动画中.
  /** @type { function(Panel) : void } */
  onAnimationFinished = (p) => null;

  constructor(param) {
    this.zrender = param.zrender;
    this.zr = param.zr;
    this.danmaku = param.danmaku;
    this.top = param.top;
    this.context = param.context;
  }

  init() {
    this.loadRuleText();

    const { zrender, zr } = this;
    zr.add(this.spRag = zrender.path.createFromString('M0 0H1640L1280 720H-360z',
      {
        position: [-2000, 0],
        style: {
          fill : '#FFF',
          shadowColor : '#FFF',
          shadowBlur : 300,
        },
        zlevel: 1
      }
    ));
    zr.add(this.spBgw = new zrender.Rect({
      shape : { width : 1280, height : 720 },
      style : {
        fill : new this.zrender.LinearGradient(
          0, 0, 0, 1,
          [
            {offset:0.1, color:'#EAFDFF'},
            {offset:0.45, color:'#DCF7FF'},
            {offset:0.8, color:'#E1EEFF'},
            {offset:1, color:'#ECEFF9'}
          ]
        ),
      },
      zlevel : -1, z : -10
    }));
    zr.add(this.spTopBar = new zrender.Rect({
      shape : { width : 1280, height : 8 },
      style : {
        fill : new this.zrender.LinearGradient(
          0, 0, 0, 1,
          [
            {offset:0.1, color:'#888'},
            {offset:0.7, color:'#999'},
            {offset:0.9, color:'#AAA'}
          ]
        ),
      },
      zlevel : 1, z : -5
    }));
    zr.add(this.spTopBarR = new zrender.Rect({
      shape : { x : 0, width : 1280, height : 7 },
      style : { fill : '#5BF' },
      zlevel : 1, z : -4
    }));
    zr.add(this.spTopText = new zrender.Text({
      position : [layout.topText1.x, layout.topText1.y],
      style : {
        text : '倒计时', fontSize : layout.topText1.fontSize, fontWeight: 'bold', textFill: '#0005', textAlign: 'right', textVerticalAlign: 'top'
      },
      zlevel : 1, z : -5
    }));
    zr.add(this.spTopNum = new zrender.Text({
      position : [layout.topText1.numX, layout.topText1.numY],
      style : {
        text : '0', fontSize : layout.topText1.numFontSize, fontWeight: 'bold', textFill: '#0005', textAlign: 'right', textVerticalAlign: 'top'
      },
      zlevel : 1, z : -5
    }));
    zr.add(this.spBottomBg = new zrender.Rect({
      shape : { x : 0, y : 680, width : 1280, height : 30 },
      style : { fill : '#0005' },
      zlevel : 1, z : -4
    }));
    zr.add(this.spBottomText = new zrender.Text({
      position : [1280, 685],
      style : {
        text : this.ruleText, fontSize : 24, fontWeight: 'bold', textFill: '#FFF', textVerticalAlign: 'top'
      },
      zlevel : 1, z : -3
    }));
    this.bottomUpdateTime = new Date().getTime();

    // 场景一的
    zr.add(this.spBg1 = zrender.path.createFromString('M0 0H840L480 720H0z',
      {
        style : {
          fill : layout.colors.stage1Bg
        },
        zlevel : -1
      }
    ));
    zr.add(this.spBg1d = zrender.path.createFromString('M-100-100H1010L550 820H-100z',
      {
        style : {
          fill : layout.colors.stage1BgD1,
          shadowColor : '#0006',
          shadowBlur : 160,
        },
        zlevel : -1, z : -2
      }
    ));
    zr.add(this.spTitle1 = new zrender.Text({
      style : {
        text : '弹幕抽奖未开始', fontSize : layout.title1.fontSize, fontWeight: 'bold', textFill: '#000B', textAlign: 'left', textVerticalAlign: 'top'
      },
      position: [layout.title1.x, layout.title1.y]
    }));
    zr.add(this.spSubTitle1a = new zrender.Text({
      style : {
        text : '发送弹幕“', fontSize : layout.subTitle1.fontSize, fontWeight: 'bold', textFill: '#FFF', textAlign: 'left', textVerticalAlign: 'top',
        textShadowColor : '#000', textShadowOffsetX : 2, textShadowOffsetY : 2
      },
      position: [layout.subTitle1.x, layout.subTitle1.y]
    }));
    zr.add(this.spWords = new zrender.Text({
      style : {
        text : '', fontSize : layout.subTitle1.wordsFontSize, fontWeight: 'bold', textFill: '#FF7', textAlign: 'left', textVerticalAlign: 'top',
        textShadowColor : '#000', textShadowOffsetX : 2, textShadowOffsetY : 2
      },
      position: [layout.subTitle1.x + this.spSubTitle1a.getBoundingRect().width + layout.subTitle1.paddingX, layout.subTitle1.y - 2]
    }));
    zr.add(this.spSubTitle1b = new zrender.Text({
      style : {
        text : '”参与抽奖', fontSize : layout.subTitle1.fontSize, fontWeight: 'bold', textFill: '#FFF', textAlign: 'left', textVerticalAlign: 'top',
        textShadowColor : '#000', textShadowOffsetX : 2, textShadowOffsetY : 2
      },
      position: [0, layout.subTitle1.y] // x 一定会改
    }));
    zr.add(this.spNum1 = new zrender.Text({
      style : {
        text : '0', fontSize : layout.numText1.fontSize, fontWeight: 'bold', textFill: '#0001', textAlign: 'left', textVerticalAlign: 'bottom',
      },
      position: [layout.numText1.x, layout.numText1.y],
      zlevel : -1, z : -4
    }));
    zr.add(this.spImg1 = new zrender.Image({
      style : {
        image : this.context.assetPath('assets/Addy.png'), shadowColor : '#000', shadowOffsetX : 10, shadowOffsetY : 10, shadowBlur : 30
      },
      position: [layout.addyImg1.x, layout.addyImg1.y],
      scale: [0.5, 0.5],
      zlevel : -1, z : 1
    }));

    // 场景二的
    // 暂时用 Rect 替代
    zr.add(this.spBg2 = zrender.path.createFromString('M390 0H1250L890 720H30z',
      {
        style : { fill : layout.colors.stage2Bg },
        zlevel : -1
      }
    ));
    zr.add(this.spBg2d = zrender.path.createFromString('M280 -100H1460L1000 820H-180z',
      {
        style : { fill : layout.colors.stage2BgD1, shadowColor : '#0006', shadowBlur : 160, },
        zlevel : -1, z : -2
      }
    ));
    zr.add(this.spTitle2 = new zrender.Text({
      style : {
        text : '看看谁是幸运儿', fontSize : layout.title2.fontSize, fontWeight: 'bold', textFill: '#000B', textAlign: 'center', textVerticalAlign: 'top'
      },
      position: [0, layout.title2.y] // x 一定会改
    }));

    this.spBg2.hide();
    this.spBg2d.hide();
    this.spTitle2.hide();
  }

  loadRuleText() {
    let t1 = '今天第一次抽奖的，规则是：  ' + this.arcade.rules.filter(r => r.type === 'start').map(r => '◆ ' + r.toString()).join('；  ') + '。';
    let t2 = '已经抽过奖的，分数在上一轮结束的基础上（未扣分前）：  ' + this.arcade.rules.filter(r => r.type === 'stage').map(r => '◆ ' + r.toString()).join('；  ') + '。';
    this.ruleText = `${t1}      ${t2}`;
  }

  setWords(word) {
    this.word = word;
    if (word === '') {
      this.spWords.hide();
      this.spSubTitle1b.hide();
      this.spSubTitle1a.attr({ style : { text : '发送任何弹幕参与抽奖' } });
      return;
    }
    this.spWords.show();
    this.spSubTitle1b.show();
    this.spSubTitle1a.attr({ style : { text : '发送弹幕“' } });
    this.spWords.attr({ style : { text : word } });
    let offX = layout.subTitle1.x + this.spSubTitle1a.getBoundingRect().width + layout.subTitle1.paddingX * 2 + this.spWords.getBoundingRect().width;
    this.spSubTitle1b.attr({ position : [offX, layout.subTitle1.y] });
  }

  destroy() {
    this.chunks.forEach((c) => c.destroy());
    this.chunks = [];
    if (this.zr) {
      this.zr.dispose();
    }
  }

  tick() {
    this.chunks.forEach(c => c.tick());

    // 倒计时部分
    if (this.currentStage == 1 && this.arcade.countdown > 0) {
      let remain = this.arcade.countdown - new Date().getTime(); // 单位 ms
      this.spTopNum.attr({ style : { text : String((remain / 1000) | 0) } });
      this.setTopBarRate(remain / this.arcade.countdownMax, false);
    }

    // 下面的文本部分
    {
      let leftPos = this.spBottomText.position.concat();
      let rect = this.spBottomText.getBoundingRect();
      let now = new Date().getTime();
      if (leftPos[0] + rect.width < 0) {
        this.spBottomText.attr({ position : [1280, leftPos[1]] });
      } else {
        this.spBottomText.attr({ position : [leftPos[0] - Math.min(now - this.bottomUpdateTime, 100) * 0.05, leftPos[1]] });
      }
      this.bottomUpdateTime = now;
    }
  }

  isReady() {
    return this.nextStage == 0;
  }

  reset() {
    this.spTitle1.attr({ style : { text : '弹幕抽奖未开始' } });
    this.nextStage = 1; // 1 -> 1 也是有可能的
    this.participantPermit = false;
    this.animateTransition(() => {
      this.chunks.forEach((c) => c.destroy());
      this.chunks = [];
      this.showStage1();
    }, () => {
      this.onAnimationFinished(this);
    });
  }

  begin() {
    // 开始发弹幕吧
    this.spTitle1.attr({ style : { text : '弹幕抽奖开始' } });
    if (this.currentStage != 1) {
      this.nextStage = 1;
      this.animateTransition(() => {
        this.chunks.forEach((c) => c.destroy());
        this.chunks = [];
        this.showStage1();
        // 从这里可以接收添加参与者的操作了
        this.participantPermit = true;
        this.arcade.participants.forEach((c) => this.addParticipant(c));
      }, () => {
        this.onAnimationFinished(this);
      });
    } else {
      this.participantPermit = true;
    }
  }

  ready() {
    // 发弹幕结束
    this.participantPermit = false;
    this.nextStage = 2;
    this.animateTransition(() => {
      this.showStage2();
      // 把 chunks 重新摆位置
      this.moveChunksAtStage2();
    }, () => {
      this.onAnimationFinished(this);
    });
  }

  showStage1() {
    this.spBg1.show();
    this.spBg1.attr({position : [-300, 0]});
    this.spBg1.animateTo({position : [0, 0]}, 800, 'exponentialOut', () => null);
    this.spBg1d.show();
    this.spBg1d.attr({position : [-300, 0]});
    this.spBg1d.animateTo({position : [0, 0]}, 800, 'exponentialOut', () => null);
    this.spTitle1.show();
    this.spTitle1.attr({position : [layout.title1.x - 300, layout.title1.y]});
    this.spTitle1.animateTo({position : [layout.title1.x, layout.title1.y]}, 800, 'exponentialOut', () => null);
    this.spSubTitle1a.show();
    this.spSubTitle1a.attr({position : [layout.subTitle1.x - 300, layout.subTitle1.y]});
    this.spSubTitle1a.animateTo({position : [layout.subTitle1.x, layout.subTitle1.y]}, 800, 'exponentialOut', () => null);
    if (this.word != '') {
      let x = layout.subTitle1.x + this.spSubTitle1a.getBoundingRect().width + layout.subTitle1.paddingX;
      this.spWords.show();
      this.spWords.attr({position : [x - 300, layout.subTitle1.y]});
      this.spWords.animateTo({position : [x, layout.subTitle1.y]}, 800, 'exponentialOut', () => null);
      x += this.spWords.getBoundingRect().width + layout.subTitle1.paddingX;
      this.spSubTitle1b.show();
      this.spSubTitle1b.attr({position : [x - 300, layout.subTitle1.y]});
      this.spSubTitle1b.animateTo({position : [x, layout.subTitle1.y]}, 800, 'exponentialOut', () => null);
    }
    this.spNum1.show();
    this.spNum1.attr({position : [layout.numText1.x - 100, layout.numText1.y], style : { text : String(this.chunks.length) }});
    this.spNum1.animateTo({position : [layout.numText1.x, layout.numText1.y]}, 800, 'exponentialOut', () => null);
    this.spImg1.show();
    this.spImg1.attr({position : [layout.addyImg1.x - 100, layout.addyImg1.y]});
    this.spImg1.animateTo({position : [layout.addyImg1.x, layout.addyImg1.y]}, 800, 'exponentialOut', () => null);

    this.spBg2.hide();
    this.spBg2d.hide();
    this.spTitle2.hide();

    this.spTopText.attr({
      position : [layout.topText1.x - 150, layout.topText1.y],
      style : { text : '倒计时' },
    });
    this.spTopText.animateTo({position : [layout.topText1.x, layout.topText1.y]}, 800, 'exponentialOut', () => null);
    this.spTopNum.attr({
      position : [layout.topText1.numX - 150, layout.topText1.numY],
      style : { text : '0' },
    });
    this.spTopNum.animateTo({position : [layout.topText1.numX, layout.topText1.numY]}, 800, 'exponentialOut', () => null);
    this.setTopBarRate(1, false);
  }

  showStage2() {
    this.spBg1.hide();
    this.spBg1d.hide();
    this.spTitle1.hide();
    this.spSubTitle1a.hide();
    this.spWords.hide();
    this.spSubTitle1b.hide();
    this.spNum1.hide();
    this.spImg1.hide();

    this.spBg2.show();
    this.spBg2.attr({position : [-300, 0]});
    this.spBg2.animateTo({position : [0, 0]}, 800, 'exponentialOut', () => null);
    this.spBg2d.show();
    this.spBg2d.attr({position : [-300, 0]});
    this.spBg2d.animateTo({position : [0, 0]}, 800, 'exponentialOut', () => null);
    this.spTitle2.show();
    this.spTitle2.attr({position : [layout.title2.x - 300, layout.title2.y]});
    this.spTitle2.animateTo({position : [layout.title2.x, layout.title2.y]}, 800, 'exponentialOut', () => null);

    this.spTopText.attr({
      position : [layout.topText1.x - 150, layout.topText1.y],
      style : { text : '幸存人数' },
    });
    this.spTopText.animateTo({position : [layout.topText1.x, layout.topText1.y]}, 800, 'exponentialOut', () => null);
    this.spTopNum.attr({
      position : [layout.topText1.numX - 150, layout.topText1.numY],
      style : { text : String(this.arcade.participants.length) },
    });
    this.spTopNum.animateTo({position : [layout.topText1.numX, layout.topText1.numY]}, 800, 'exponentialOut', () => null);
    this.setTopBarRate(1, false);
  }

  /**
   * @param {Customer} customer 
   */
  addParticipant(customer) {
    if (!this.participantPermit) return;

    const { startX, startY, offX, offY, colCount, colOffX, colOffY } = layout.chunks1;

    let len = this.chunks.length;
    let x = startX + (offX * (len % colCount));
    let y = startY + (offY * (len % colCount)) + (colOffY * parseInt(len / colCount));
    let chunk = new Chunk({
      zrender : this.zrender,
      zr : this.zr,
      top : this.top,
      context : this.context,
      customer,
    }, x, y);
    if (len && len % colCount == 0) {
      // 前面几列的 chunk 需要重新改位置了
      let firstX = startX + (colOffX * parseInt(len / colCount));
      this.chunks.forEach((c, index) => c.animateTo(
        firstX + (offX * (index % colCount)) - (colOffX * parseInt(index / colCount)), c.y
      ));
    }
    this.chunks.push(chunk);
    this.spNum1.attr({ style : { text : String(this.chunks.length) } });
  }

  /**
   * @param {Customer} customer 
   */
  updateParticipant(customer) {
    let chunk = this.chunks.find(c => c.customer == customer);
    if (chunk) {
      chunk.onCustomerUpdated();
    }
  }

  animateTransition(middleFunc, finishFunc) {
    // 大白布从左往右扫过, 当扫到中间位置时做点 middleFunc, 比如把背景换了;
    // 结束时候调用 finishFunc
    this.spRag.attr({position : [-2000, 0]});
    this.spRag.animateTo({position : [0, 0]}, 200, () => {
      if (middleFunc) middleFunc.call(this);
      this.spRag.animateTo({position : [2000, 0]}, 200, () => {
        this.currentStage = this.nextStage;
        this.nextStage = 0;
        if (finishFunc) finishFunc.call(this);
      });
    });
  }

  /**
   * 场景 1 跳到 2 时, 需要把 chunk 的位置全改位置
   */
  moveChunksAtStage2() {
    const { startX, startY, offX, offY, colCount, colOffX, colOffY } = layout.chunks2;
    this.chunks.forEach((c, index) => {
      const x = startX + (offX * (index % colCount)) + (colOffX * parseInt(index / colCount));
      const y = startY + (offY * (index % colCount)) + (colOffY * parseInt(index / colCount));
      c.moveTo(x - 300, y);
      c.animateTo(x, y);
      c.turnToStage2();
    });
  }

  /**
   * 清理掉淘汰的 chunk, 重新排位置
   */
  beforeLottery() {
    for (let i = this.chunks.length - 1; i >= 0; i--) {
      let c = this.chunks[i];
      if (c.hp == 0) {
        c.toDestroy();
        this.chunks.splice(i, 1);
      }
    }

    const { startX, startY, offX, offY, colCount, colOffX, colOffY } = layout.chunks2;
    this.chunks.forEach((c, index) => {
      c.animateTo(
        startX + (offX * (index % colCount)) + (colOffX * parseInt(index / colCount)),
        startY + (offY * (index % colCount)) + (colOffY * parseInt(index / colCount))
      );
    });
  }

  afterLottery() {
    this.chunks.forEach(c => c.hpDown());
    this.spTopNum.attr({ style : { text : String(this.arcade.candidates.length) } });
    this.setTopBarRate(this.arcade.candidates.length / this.arcade.participants.length, true);
  }

  /**
   * @param {number} rate  0-1 之间的数字
   * @param {boolean} animate  是否有动画
   */
  setTopBarRate(rate, animate) {
    if (animate) {
      this.spTopBarR.animateTo({ shape : { x : (1 - rate) * 640, width : 1280 * rate } }, 1000, 'quarticOut', () => null);
    } else {
      this.spTopBarR.attr({ shape : { x : (1 - rate) * 640, width : 1280 * rate } });
    }
    
  }
}

class Chunk {
  outline = 'M24 0H136a24,24 0 0,1 0,48H24a24,24 0 0,1 0,-48z'; // 圆角方形, 锚点在左上角
  /** @type {Customer} */
  customer;
  createTime = new Date().getTime();
  stage = 1;

  style = 0; toStyle = -1; // 有两种 style, 1 是白色, 0 是黑色.
  styleChangeTime; // 颜色开始变换的时间点

  spBg; spOutline;
  spAvatar; spAvatarClip;
  spGrads1; spGrads2; spGradClip1; spGradClip2;
  spName;
  spHp; hp = 0; // 显示 HP

  /**
   * @param {any} param 
   * @param {number} x 圆角方形左上角 x
   * @param {number} y 圆角方形左上角 y
   */
  constructor(param, x, y) {
    this.zrender = param.zrender;
    this.zr = param.zr;
    this.top = param.top;
    this.context = param.context;
    this.customer = param.customer;

    this.x = x;
    this.y = y;
    this.init();
  }

  init() {
    const { zr, zrender, outline, customer, x, y } = this;
    zr.add(this.spBg = zrender.path.createFromString(outline,
      {
        position: [x, y],
        style: { fill : '#FFF', shadowColor : '#000', shadowBlur : 6 },
        z: -10
      }
    ));
    zr.add(this.spOutline = zrender.path.createFromString(outline,
      {
        position: [x, y],
        style: { fill : '#0000', stroke : '#FFF', lineWidth : 4 },
        z: 10
      }
    ));

    this.spAvatarClip = zrender.path.createFromString('M24 0H96l-12 48H24a24,24 0 0,1 0,-48z',
      {
        position: [0, 24], // 相对于 spAvatar position 的位置
        style: { fill : '#FFF' }
      }
    );
    this.spAvatar = new zrender.Image({
      style: {
        image: fetchAvatarUrl(customer.uId),
        width: 96, height: 96, opacity: 0.8
      },
      position: [x, y - 24],
      z: 0
    });
    this.spAvatar.setClipPath(this.spAvatarClip);
    zr.add(this.spAvatar);

    this.spGradClip1 = zrender.path.createFromString(outline,
      {
        position: [0, 0],
        style: { fill : '#FFF' },
        z: 2
      }
    );
    this.spGrads1 = zrender.path.createFromString(
      'M97  0h-21l-0.5 2h21z  M96  4h-21l-0.5 2h21z  M95  8h-21l-0.5 2h21z  M94 12h-21l-0.5 2h21z' +
      'M93 16h-21l-0.5 2h21z  M92 20h-21l-0.5 2h21z  M91 24h-21l-0.5 2h21z  M90 28h-21l-0.5 2h21z' +
      'M89 32h-21l-0.5 2h21z  M88 36h-21l-0.5 2h21z  M87 40h-21l-0.5 2h21z  M86 44h-21l-0.5 2h21z' +
      'M85 48h-21l-0.5 2h21z  M84 52h-21l-0.5 2h21z  M83 56h-21l-0.5 2h21z  M82 60h-21l-0.5 2h21z',
      {
        position: [x, y], // 相对于 spAvatar position 的位置
        style: { fill : '#FFF' }
      }
    );
    this.spGrads1.setClipPath(this.spGradClip1);
    zr.add(this.spGrads1);

    this.spGradClip2 = zrender.path.createFromString(outline,
      {
        position: [0, 0],
        style: { fill : '#FFF' },
        z: 2
      }
    );
    this.spGrads2 = zrender.path.createFromString(
      'M103 -24h-11l-1.5 6h11z  M100 -12h-11l-1.5 6h11z  M97  0h-11l-1.5 6h11z  M94 12h-11l-1.5 6h11z' +
      'M91   24h-11l-1.5 6h11z  M88 36h-11l-1.5 6h11z    M85 48h-11l-1.5 6h11z  M82 60h-11l-1.5 6h11z',
      {
        position: [x, y], // 相对于 spAvatar position 的位置
        style: { fill : '#FFF' }
      }
    );
    this.spGrads2.setClipPath(this.spGradClip2);
    zr.add(this.spGrads2);

    this.spName = new zrender.Text({
      style : {
        text : customer.uName,
        textVerticalAlign: 'bottom',
        textAlign: 'right',
        fontWeight: 'bold',
        fontSize: 12,
        fontFamily: 'Microsoft YaHei UI',
        textFill: '#000',
        textShadowColor: '#FFF',
        textShadowOffsetX: 1,
        textShadowOffsetY: 1
      },
      position: [ x + 136, y + 16 ],
      z: 10
    })
    zr.add(this.spName);

    this.spHp = new zrender.Text({
      style : {
        text : "",
        textVerticalAlign: 'bottom',
        textAlign: 'right',
        fontWeight: 'bold',
        fontSize: 25,
        fontFamily: 'Microsoft YaHei UI',
        textFill: '#000',
        textShadowColor: '#FFF',
        textShadowBlur: 3
      },
      position: [ x + 136, y + 47 ],
      z: 10
    })
    zr.add(this.spHp);
  }

  /**
   * @param {number} x 
   * @param {number} y `
   */
  moveTo(x, y) {
    this.x = x;
    this.y = y;
    if (this.spBg) {
      this.spBg.stopAnimation(true);
      this.spBg.attr({position : [x, y]});
      this.spOutline.stopAnimation(true);
      this.spOutline.attr({position : [x, y]});
      this.spAvatar.stopAnimation(true);
      this.spAvatar.attr({position : [x, y - 24]});
      this.spName.stopAnimation(true);
      this.spName.attr({position : [x + 136, y + 16]});
      this.spHp.stopAnimation(true);
      this.spHp.attr({position : [x + 136, y + 47]});
    }
  }

  animateTo(x, y) {
    this.x = x;
    this.y = y;
    if (this.spBg) {
      this.spBg.stopAnimation(true);
      this.spBg.animateTo({position : [x, y]}, 500, 'quadraticOut', () => null);
      this.spOutline.stopAnimation(true);
      this.spOutline.animateTo({position : [x, y]}, 500, 'quadraticOut', () => null);
      this.spAvatar.stopAnimation(true);
      this.spAvatar.animateTo({position : [x, y - 24]}, 500, 'quadraticOut', () => null);
      this.spName.stopAnimation(true);
      this.spName.animateTo({position : [x + 136, y + 16]}, 500, 'quadraticOut', () => null);
      this.spHp.stopAnimation(true);
      this.spHp.animateTo({position : [x + 136, y + 47]}, 500, 'quadraticOut', () => null);
    }
  }

  toDestroy() {
    if (this.spBg) {
      this.spBg.stopAnimation(true);
      this.spBg.animateTo({style : {opacity : 0}}, 200, () => {this.destroy()});
      this.spOutline.stopAnimation(true);
      this.spOutline.animateTo({style : {opacity : 0}}, 200, () => null);
      this.spAvatar.stopAnimation(true);
      this.spAvatar.animateTo({style : {opacity : 0}}, 200, () => null);
      this.spName.stopAnimation(true);
      this.spName.animateTo({style : {opacity : 0}}, 200, () => null);
      this.spHp.stopAnimation(true);
      this.spHp.animateTo({style : {opacity : 0}}, 200, () => null);
    }
  }

  destroy() {
    const { zr } = this;
    if (this.spBg) zr.remove(this.spBg);
    if (this.spOutline) zr.remove(this.spOutline);
    if (this.spAvatar) zr.remove(this.spAvatar);
    if (this.spGrads1) zr.remove(this.spGrads1);
    if (this.spGrads2) zr.remove(this.spGrads2);
    if (this.spName) zr.remove(this.spName);
    if (this.spHp) zr.remove(this.spHp);
  }

  tick() {
    const now = new Date().getTime();
    let gradOff = (now - this.createTime) % 1000 / 1000;
    const x = this.spBg.position[0], y = this.spBg.position[1];
    this.spGrads1.attr({ position : [x + gradOff, y - 4 * gradOff] });
    this.spGradClip1.attr({ position : [- gradOff, 4 * gradOff] });
    
    this.spGrads2.attr({ position : [x - 3 * gradOff, y + 12 * gradOff] });
    this.spGradClip2.attr({ position : [3 * gradOff, -12 * gradOff] });

    // style 变化部分
    if (this.toStyle != -1) {
      let delta = (now - this.styleChangeTime) / 400;
      delta = delta > 1 ? 1 : delta;
      this.style = (this.toStyle == 1) ? delta : (1 - delta);
      let style255 = parseInt(this.style * 255);

      const colorA = `rgb(${style255}, ${style255}, ${style255})`;
      const colorB = `rgb(${255 - style255}, ${255 - style255}, ${255 - style255})`;
      this.spBg.attr({ style : { fill : colorA } });
      this.spOutline.attr({ style : { stroke : colorA } });
      this.spGrads1.attr({ style : { fill : colorA } });
      this.spGrads2.attr({ style : { fill : colorA } });
      this.spName.attr({ style : { textFill : colorB, textShadowColor : colorA } });
      this.spHp.attr({ style : { textFill : colorB, textShadowColor : colorA } });

      if (this.style == 0) {
        if (this.hp == 0) {
          this.toStyle = -1;
          this.spHp.attr({ style : { text : '淘汰' } });
        } else {
          this.toStyle = 1;
          this.styleChangeTime = now;
          this.spHp.attr({ style : { text : String(this.hp) } });
        }
      } else if (this.style == 1) {
        this.toStyle = -1;
      }
    }
  }

  turnToStage1() {
    // 转到 stage 1 之后, HP 要隐藏
    this.stage = 1;
    this.spHp.hide();
  }

  turnToStage2() {
    // 转到 stage 2 之后, HP 要显示
    this.stage = 2;
    this.hp = this.customer.hp;
    this.spHp.show();
    this.spHp.attr({ style : { text : this.hp == 0 ? '淘汰' : String(this.hp) } });
  }

  /**
   * stage 1, 在 customer 的舰长、房管、等级数据变化时触发.
   */
  onCustomerUpdated() {
    // TODO 
  }

  /**
   * stage 2, 每一次抽奖的扣血. 如果扣血了就会有动画.
   */
  hpDown() {
    if (this.hp == this.customer.hp || this.hp == 0) {
      return;
    }
    this.toStyle = 0;
    this.styleChangeTime = new Date().getTime();
    this.spHp.attr({ style : { text : `-${this.hp - this.customer.hp}` } });
    this.hp = this.customer.hp;
  }

}

export {
  Arcade, Panel,
  PHASE_NO_READY, PHASE_SEND_DANMU, PHASE_SEND_CLOSE, PHASE_BEGIN_LOTTERY, PHASE_FINISH_LOTTERY
};
